#include <iostream>
#include <fstream>
#include <string>
#include <cassert>

#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/internet-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/applications-module.h"
#include "ns3/flow-monitor-helper.h" //流监控头文件声明
#include "ns3/ipv4-global-routing-helper.h"
#include "ns3/netanim-module.h" // 网络仿真动画追踪头文件声明

using namespace ns3;

AnimationInterface* pAnim = 0; //仿真动画指针

const std::string outputDir = "./scratch/"; //指定数据输出到scratch文件夹
//指定数据输出的文件名，后续将根据不同文件类型采用不同后缀
const std::string fileName = "data-E2ePathCongestionDetection";


int main(int argc, char* argv[])
{
	bool enableTrace = false; // 默认关闭网络报文转发追踪trace
	bool enableAnimation = false; // 默认关闭网络仿真动画追踪 netanim
	bool enableFlowMonitor = true; //默认启用流量监控 flowmonitor

	//定义可通过命令行控制的仿真参数
	uint16_t pktSize = 512; //探测流：默认字节大小
	uint32_t probeHz = 1000; //探测流：默认发包频率

	CommandLine cmd(); //开启命令行参数
	cmd.AddValue("pktSize", "Indicate the pakcet size of each probe.", pktSize);
	cmd.AddValue("probeHz", "Indicate the probing frequency.", probeHz);
	cmd.Parse(argc, argv);

	// 创建网络节点
	NodeContainer srcNodes;
	NodeContainer rtNodes;
	NodeContainer dstNodes;

	srcNodes.Create(3); //源节点
	rtNodes.Create(2); //中间节点
	dstNodes.Create(3); //目的节点

	//给节点配置协议栈
	NodeContainer allNodes = NodeContainer(srcNodes, dstNodes, rtNodes);

	InternetStackHelper internet;
	internet.Install(allNodes);

	//设置网络链路属性
	PointToPointHelper p2p;
	p2p.SetDeviceAttribute("DataRate", StringValue("10Mb/s")); // ᬟ边缘链路带宽与时延୊
	p2p.SetChannelAttribute("Delay", StringValue("2ms"));

	NetDeviceContainer s0r0 = p2p.Install(NodeContainer(srcNodes.Get(0), rtNodes.Get(0)));
	NetDeviceContainer s1r0 = p2p.Install(NodeContainer(srcNodes.Get(1), rtNodes.Get(0)));
	NetDeviceContainer s2r0 = p2p.Install(NodeContainer(srcNodes.Get(2), rtNodes.Get(0)));

	NetDeviceContainer r1d0 = p2p.Install(NodeContainer(rtNodes.Get(1), dstNodes.Get(0)));
	NetDeviceContainer r1d1 = p2p.Install(NodeContainer(rtNodes.Get(1), dstNodes.Get(1)));
	NetDeviceContainer r1d2 = p2p.Install(NodeContainer(rtNodes.Get(1), dstNodes.Get(2)));

	p2p.SetDeviceAttribute("DataRate", StringValue("10Mb/s")); // 中间链路带宽与时延
	p2p.SetChannelAttribute("Delay", StringValue("10ms"));
	p2p.SetQueue("ns3::DropTailQueue<Packet>", "MaxSize", QueueSizeValue(QueueSize("150p"))); // 队列缓存大小

	NetDeviceContainer r0r1 = p2p.Install(NodeContainer(rtNodes.Get(0), rtNodes.Get(1)));

	// 配置ipv4地址
	Ipv4AddressHelper ipv4;
	ipv4.SetBase("10.1.1.0", "255.255.255.0"); // 定义网络地址
	Ipv4InterfaceContainer is0r0 = ipv4.Assign(s0r0); // 分配处于该网络中，各节点网卡的IP地址

	ipv4.SetBase("10.1.2.0", "255.255.255.0");
	Ipv4InterfaceContainer is1r0 = ipv4.Assign(s1r0);

	ipv4.SetBase("10.1.3.0", "255.255.255.0");
	Ipv4InterfaceContainer is2r0 = ipv4.Assign(s2r0);

	ipv4.SetBase("10.1.4.0", "255.255.255.0");
	Ipv4InterfaceContainer ir0r1 = ipv4.Assign(r0r1);

	ipv4.SetBase("10.1.5.0", "255.255.255.0");
	Ipv4InterfaceContainer ir1d0 = ipv4.Assign(r1d0);

	ipv4.SetBase("10.1.6.0", "255.255.255.0");
	Ipv4InterfaceContainer ir1d1 = ipv4.Assign(r1d1);

	ipv4.SetBase("10.1.7.0", "255.255.255.0");
	Ipv4InterfaceContainer ir1d2 = ipv4.Assign(r1d2);

	Ipv4GlobalRoutingHelper::PopulateRoutingTables(); //启用全局路由

	//创建和配置探测流：基于onoff(UDP)应用
	uint16_t port = 9; // Discard port (RFC 863)
	OnOffHelper onoff("ns3::UdpSocketFactory",
		Address(InetSocketAddress(ir1d0.GetAddress(1), port)));

	std::string probingRate = std::to_string(probeHz * 8 * pktSize) + std::string("b/s");

	onoff.SetConstantRate(DataRate(probingRate), pktSize);

	ApplicationContainer onoffApps = onoff.Install(srcNodes.Get(0));
	onoffApps.Start(Seconds(1.25));
	onoffApps.Stop(Seconds(1.75));

	PacketSinkHelper onoff_sink("ns3::UdpSocketFactory",
		Address(InetSocketAddress(Ipv4Address::GetAny(), port)));
	ApplicationContainer onoffSink = onoff_sink.Install(dstNodes.Get(0));
	onoffSink.Start(Seconds(1.0));
	onoffSink.Stop(Seconds(2.5));


	//配置背景-拥塞流 (PPBP, https://github.com/sharan-naribole/PPBP-ns3)
	uint16_t ppbp_port = 9;

	PPBPHelper ppbp = PPBPHelper("ns3::UdpSocketFactory",
		InetSocketAddress(ir1d1.GetAddress(1), ppbp_port));

	ppbp.SetAttribute("BurstIntensity", DataRateValue(DataRate("0.5Mb/s"))); //
	ppbp.SetAttribute("PacketSize", UintegerValue(512));
	ppbp.SetAttribute("MeanBurstArrivals", StringValue("ns3::ConstantRandomVariable[Constant=100.0]"));
	ppbp.SetAttribute("MeanBurstTimeLength", StringValue("ns3::ConstantRandomVariable[Constant=0.1]"));

	ApplicationContainer ppbpApps = ppbp.Install(srcNodes.Get(1));
	ppbpApps.Start(Seconds(1.0));
	ppbpApps.Stop(Seconds(2.0));

	Ptr<Socket> ppbpSink = Socket::CreateSocket(dstNodes.Get(1), UdpSocketFactory::GetTypeId());
	InetSocketAddress local = InetSocketAddress(ir1d1.GetAddress(1), ppbp_port);
	ppbpSink->Bind(local);

	//配置应用流（BulkSendApplication，基于tcp；模拟大文件的网络传输）
	uint16_t bs_port = 9; // 网络唤醒端口 参考：https://wenku.baidu.com/view/d921383359fafab069dc5022aaea998fcc2240cd.html
	BulkSendHelper bulk("ns3::TcpSocketFactory",
						InetSocketAddress(ir1d2.GetAddress(1), bs_port));
	
	uint32_t maxBytes = 0; // 设置发送数据的总量，0表示不限
	bulk.SetAttribute("MaxBytes", UintegerValue(maxBytes));
	
	ApplicationContainer bulkApps = bulk.Install(srcNodes.Get(2));
	bulkApps.Start(Seconds(0.0));
	bulkApps.Stop(Seconds(3.0));
	
	PacketSinkHelper bulk_sink("ns3::TcpSocketFactory",
								InetSocketAddress(Ipv4Address::GetAny(), bs_port));
	ApplicationContainer bulkSink = bulk_sink.Install(dstNodes.Get(2));
	bulkSink.Start(Seconds(0.0));
	bulkSink.Stop(Seconds(3.5));
	
	//配置追踪仿真实验各种报文的详细收发情况
	if (enableTrace) {
		AsciiTraceHelper ascii;
		p2p.EnableAsciiAll(ascii.CreateFileStream(outputDir + fileName + ".tr"));
	}

	
	//创建和配置animation文件 路由器等图标文件存放于ns-allinone-3.34/ns-3.34/icons/
	if (enableAnimation) {
		AnimationInterface::SetConstantPosition(srcNodes.Get(0), 2, 2); // 设置节点在NetAnim中的固定位置
		AnimationInterface::SetConstantPosition(srcNodes.Get(1), 2, 7);
		AnimationInterface::SetConstantPosition(srcNodes.Get(2), 2, 12);
		AnimationInterface::SetConstantPosition(rtNodes.Get(0), 7, 7);
		AnimationInterface::SetConstantPosition(rtNodes.Get(1), 17, 7);
		AnimationInterface::SetConstantPosition(dstNodes.Get(0), 22, 2);
		AnimationInterface::SetConstantPosition(dstNodes.Get(1), 22, 7);
		AnimationInterface::SetConstantPosition(dstNodes.Get(2), 22, 12);
		
		pAnim = new AnimationInterface(outputDir + fileName + ".xml");
		
		uint32_t router_img = pAnim->AddResource("./icons/router.png"); // 指定路由器节点的图标；支持png不支持jpg
		uint32_t server_img = pAnim->AddResource("./icons/server.png");
		uint32_t client_img = pAnim->AddResource("./icons/pc.png");
		
		pAnim->UpdateNodeDescription(srcNodes.Get(0), "s0"); // 可自定义节点的显示名称
		pAnim->UpdateNodeDescription(srcNodes.Get(1), "s1");
		pAnim->UpdateNodeDescription(srcNodes.Get(2), "s2");
		pAnim->UpdateNodeDescription(rtNodes.Get(0), "r0");
		pAnim->UpdateNodeDescription(rtNodes.Get(1), "r1");
		pAnim->UpdateNodeDescription(dstNodes.Get(0), "d0");
		pAnim->UpdateNodeDescription(dstNodes.Get(1), "d1");
		pAnim->UpdateNodeDescription(dstNodes.Get(2), "d2");

		pAnim->UpdateNodeImage(0, client_img); //修改节点的图标为上述自定义的图标
		pAnim->UpdateNodeImage(1, client_img);
		pAnim->UpdateNodeImage(2, client_img);
		pAnim->UpdateNodeImage(3, router_img);
		pAnim->UpdateNodeImage(4, router_img);
		pAnim->UpdateNodeImage(5, server_img);
		pAnim->UpdateNodeImage(6, server_img);
		pAnim->UpdateNodeImage(7, server_img);
	}
	
	//配置流量监控器，可参考 https://www.nsnam.org/docs/models/html/flow-monitor.html
	FlowMonitorHelper flowmonHelper;
	if (enableFlowMonitor) {
		flowmonHelper.InstallAll();
	}
	
	Simulator::Stop(Seconds(3.5));
	Simulator::Run();
	
	if (enableFlowMonitor) { // 输出流监控的统计信息，不统计直方图，不记录单个报文的网络转发性能信息
		flowmonHelper.SerializeToXmlFile(outputDir + fileName + ".flowmon", false, false);
	}
	
	if (enableAnimation) { // 删除netanim动画追踪指针
		delete pAnim;
		pAnim = NULL;
	}
	
	Simulator::Destroy(); //释放仿真内存
	return 0;

}